use std::fs::File;
use std::path::Path;
use std::sync::{Arc, Mutex};

use async_std::io::{Read, Write};
use async_std::net::TcpStream;
use async_tls::server::TlsStream;
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig};
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};

/// Load the passed certificates file
fn load_certs(path: impl AsRef<Path>) -> Result<Vec<Certificate>> {
    certs(&mut std::io::BufReader::new(File::open(path)?))
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid cert"))
}

/// Load the passed keys file
fn load_keys(path: impl AsRef<Path>) -> Result<Vec<PrivateKey>> {
    rsa_private_keys(&mut std::io::BufReader::new(File::open(path)?))
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid key"))
}

/// Configure the server using rusttls
/// See https://docs.rs/rustls/0.16.0/rustls/struct.ServerConfig.html for details
///
/// A TLS server needs a certificate and a fitting private key
pub fn load_config() -> Result<ServerConfig> {
    let certs = load_certs("../tls/server.crt")?;
    let mut keys = load_keys("../tls/server.key")?;

    // we don't use client authentication
    let mut config = ServerConfig::new(NoClientAuth::new());
    config
        // set this server to use one cert together with the loaded private key
        .set_single_cert(certs, keys.remove(0))
        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?;

    Ok(config)
}

#[derive(Clone)]
pub struct TLStream(Arc<Mutex<TlsStream<TcpStream>>>);

impl TLStream {
    pub fn new(inner: Arc<Mutex<TlsStream<TcpStream>>>) -> Self {
        TLStream(inner)
    }
}

impl Read for TLStream {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<Result<usize>> {
        let mut guard = self.0.lock();
        let tls_stream = guard.as_deref_mut().unwrap();
        Pin::new(tls_stream).poll_read(cx, buf)
    }
}

impl Write for TLStream {
    fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize>> {
        let mut guard = self.0.lock();
        let tls_stream = guard.as_deref_mut().unwrap();
        Pin::new(tls_stream).poll_write(cx, buf)
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
        let mut guard = self.0.lock();
        let tls_stream = guard.as_deref_mut().unwrap();
        Pin::new(tls_stream).poll_flush(cx)
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
        let mut guard = self.0.lock();
        let tls_stream = guard.as_deref_mut().unwrap();
        Pin::new(tls_stream).poll_close(cx)
    }
}
